package cn.bblocks.cacheTest.suport;

import org.apache.commons.lang3.ClassUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.FatalBeanException;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import static org.springframework.beans.BeanUtils.getPropertyDescriptor;

/**
 * @program:
 * @author: captain.ma
 * @date: 2018-11-20
 * @since: 1.0.0.0
 */
public abstract class BeanSupport {

    public static Map<String, Object> convertMapFromBean(Object target){

        return convertMapFromBean(target, false);
    }

    public static Map<String, Object> convertMapFromBean(Object target, boolean isNull) {
        Map<String, Object> map = new HashMap<String, Object>();
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(target.getClass());

            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                // 过滤class属性
                if (!key.equals("class")) {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(target);
                    if (!isNull) {
                        if (value != null) {
                            map.put(key, value);
                        }
                    } else {
                        map.put(key, value);
                    }


                }
            }
        } catch (Exception e) {
           // ExceptionFactory.wrapException("", e);
        }
        return map;
    }


    /**
     * 循环向上转型,获取对象的DeclaredField.
     *
     * @throws NoSuchFieldException 如果没有该Field时抛出.
     */
    public static Field getDeclaredField(Object object, String propertyName)
            throws NoSuchFieldException {
        return getDeclaredField(object.getClass(), propertyName);
    }

    private static Field getDeclaredField(Class clazz, String propertyName)
            throws NoSuchFieldException {

        for (Class superClass = clazz; superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                return superClass.getDeclaredField(propertyName);
            } catch (NoSuchFieldException e) {
                // Field不在当前类定义,继续向上转型
            }
        }
        throw new NoSuchFieldException("No such field: " + clazz.getName()
                + '.' + propertyName);
    }

    public static <T> T copyProperties(Object source, T target, Consumer<T> consumer) {
        copyProperties(source, target);
        consumer.accept(target);
        return target;
    }

    public static <T> T copyProperties(Object source, T target) {
        final BeanWrapper targetBeanInfo = new BeanWrapperImpl(target);
        PropertyDescriptor[] targetPds = targetBeanInfo.getPropertyDescriptors();
        for (PropertyDescriptor targetPd : targetPds) {

            Method writeMethod = targetPd.getWriteMethod();
            String name = targetPd.getName();
            if (writeMethod != null && !"class".equals(name)) {
                PropertyDescriptor sourcePd = null;

                sourcePd = getPropertyDescriptor(source.getClass(), name);

                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (value == null) {
                                continue;
                            }
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, value);
                        } catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + name + "' from source to target", ex);
                        }
                    }
                }
            }
        }
        return target;
    }
}
